package com.enation.app.javashop.core.trade.order.service.impl;

import com.dag.eagleshop.core.account.model.dto.account.AccountDTO;
import com.dag.eagleshop.core.account.model.enums.AccountTypeEnum;
import com.dag.eagleshop.core.account.service.AccountManager;
import com.dag.eagleshop.core.account.service.AccountPaymentManager;
import com.enation.app.javashop.core.base.rabbitmq.AmqpExchange;
import com.enation.app.javashop.core.base.CachePrefix;
import com.enation.app.javashop.core.base.message.OrderStatusChangeMsg;
import com.enation.app.javashop.core.member.model.dos.Member;
import com.enation.app.javashop.core.member.service.MemberManager;
import com.enation.app.javashop.core.payment.PaymentErrorCode;
import com.enation.app.javashop.core.payment.model.dos.PaymentBillDO;
import com.enation.app.javashop.core.payment.model.dos.PaymentMethodDO;
import com.enation.app.javashop.core.payment.model.enums.PaymentPluginEnum;
import com.enation.app.javashop.core.payment.model.enums.TradeType;
import com.enation.app.javashop.core.payment.service.PaymentBillManager;
import com.enation.app.javashop.core.payment.service.PaymentMethodManager;
import com.enation.app.javashop.core.promotion.tool.model.enums.PromotionTypeEnum;
import com.enation.app.javashop.core.trade.TradeErrorCode;
import com.enation.app.javashop.core.trade.cart.model.vo.CartPromotionVo;
import com.enation.app.javashop.core.trade.cart.model.vo.CartSkuVO;
import com.enation.app.javashop.core.trade.cart.service.CartOriginDataManager;
import com.enation.app.javashop.core.trade.cart.service.CartPromotionManager;
import com.enation.app.javashop.core.trade.order.model.dos.*;
import com.enation.app.javashop.core.trade.order.model.dto.OrderDTO;
import com.enation.app.javashop.core.trade.order.model.enums.OrderOutStatusEnum;
import com.enation.app.javashop.core.trade.order.model.enums.OrderOutTypeEnum;
import com.enation.app.javashop.core.trade.order.model.enums.OrderStatusEnum;
import com.enation.app.javashop.core.trade.order.model.enums.OrderTypeEnum;
import com.enation.app.javashop.core.trade.order.model.vo.TradeVO;
import com.enation.app.javashop.core.trade.order.service.*;
import com.enation.app.javashop.framework.cache.Cache;
import com.enation.app.javashop.framework.context.UserContext;
import com.enation.app.javashop.framework.database.DaoSupport;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.security.model.Buyer;
import com.enation.app.javashop.framework.util.CurrencyUtil;
import com.enation.app.javashop.framework.util.DateUtil;
import com.enation.app.javashop.framework.util.StringUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

/**
 * 交易入库业务实现类
 *
 * @author Snow create in 2018/5/9
 * @version v2.0
 * @since v7.0.0
 */
@Service
public class TradeIntodbManagerImpl implements TradeIntodbManager {

    protected final Log logger = LogFactory.getLog(this.getClass());


    private Integer orderCacheTimeout = 60 * 60;

    @Autowired
    @Qualifier("tradeDaoSupport")
    private DaoSupport daoSupport;

    @Autowired
    private Cache cache;

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private CheckoutParamManager checkoutParamManager;


    @Autowired
    private OrderLogManager orderLogManager;

    @Autowired
    private OrderOutStatusManager orderOutStatusManager;

    @Autowired
    private CartOriginDataManager cartOriginDataManager;

    @Autowired
    private CartPromotionManager cartPromotionManager;
    /**
     * 此处有重要的事务实际应用知识，当同一个类内部两个事务方法调用的时候，不能直接调用，否则事务会不生效。by_Snow
     * 具体请看：https://segmentfault.com/a/1190000008379179
     */
    @Autowired
    private TradeIntodbManagerImpl self;

    @Autowired
    private PaymentBillManager paymentBillManager;

    @Autowired
    private PaymentMethodManager paymentMethodManager;

    @Autowired
    private AccountManager accountManager;

    @Autowired
    private MemberManager memberManager;

    @Override
    @Transactional(value = "tradeTransactionManager", propagation = Propagation.REQUIRED,
            rollbackFor = {RuntimeException.class, ServiceException.class, Exception.class})
    public void intoDB(TradeVO tradeVO) {
        try {
            self.innerIntoDB(tradeVO);

            //压入缓存
            String cacheKey = CachePrefix.TRADE_SESSION_ID_PREFIX.getPrefix() + tradeVO.getTradeSn();
            this.cache.put(cacheKey, tradeVO, orderCacheTimeout);

            //清除已购买的商品购物车数据
            List<Integer> skuIds = tradeVO.getSkuIds();
            cartOriginDataManager.delete(skuIds.toArray(new Integer[skuIds.size()]),tradeVO.getCartSourceType());

            //清除用户选择的所有的优惠券
            cartPromotionManager.cleanCoupon();

            //清空备注信息
            this.checkoutParamManager.setRemark("");

            //清空发票信息
            this.checkoutParamManager.deleteReceipt();

            //发送订单创建消息
            this.amqpTemplate.convertAndSend(AmqpExchange.ORDER_CREATE, AmqpExchange.ORDER_CREATE + "_ROUTING", cacheKey);

        } catch (Exception e) {
            logger.error("创建订单出错", e);
            throw new ServiceException(TradeErrorCode.E456.code(), "订单创建出现错误，请稍后重试");

        }
    }


    @Transactional(value = "tradeTransactionManager", propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
    public void innerIntoDB(TradeVO tradeVO) {

        if (tradeVO == null) {
            throw new RuntimeException("交易无法入库，原因：trade为空");
        }

        //校验钱包支付金额
        List<OrderDTO> orderList = tradeVO.getOrderList();
        double walletPayPrice = tradeVO.getWalletPayPrice();
        Double totalPrice = tradeVO.getPriceDetail().getTotalPrice();
        Integer memberId = tradeVO.getMemberId();
        Member member = memberManager.getModel(memberId);
        this.checkWalletPayPrice(orderList, walletPayPrice, totalPrice, member.getAccountMemberId());

        //订单mq消息
        OrderStatusChangeMsg orderStatusChangeMsg = new OrderStatusChangeMsg();

        Buyer buyer = UserContext.getBuyer();

        // 交易入库
        TradeDO tradeDO = new TradeDO(tradeVO);
        this.daoSupport.insert(tradeDO);

        long createTime = DateUtil.getDateline();
        tradeDO.setCreateTime(createTime);

        // 订单入库
        for (OrderDTO orderDTO : orderList) {

            /*
             * 计算每个商品的成交价：
             * 如果此订单使用了优惠券，要把优惠券的金额按照比例分配到每个商品的价格。
             * 比例按照此商品占此订单商品总金额的百分比。
             */
            //商品总金额
            //Double goodsTotalPrice = orderDTO.getPrice().getGoodsPrice();

            //此订单总优惠金额(包含所有活动优惠，优惠券的优惠。)
            Double discountTotalPrice = orderDTO.getPrice().getDiscountPrice();

            List<CartSkuVO> list = orderDTO.getSkuList();

            //总优惠金额 去除单品立减，团购，限时抢购优惠券的金额。
            for (CartSkuVO skuVO : list) {

                List<CartPromotionVo> singleList = skuVO.getSingleList();
                if (singleList == null) {
                    continue;
                }
                String promotionType = "";

                for (CartPromotionVo promotionGoodsVO : singleList) {
                    if (promotionGoodsVO.getIsCheck() != null && promotionGoodsVO.getIsCheck() == 1) {
                        promotionType = promotionGoodsVO.getPromotionType();
                    }
                }

                if (promotionType.equals(PromotionTypeEnum.MINUS.name())
                        || promotionType.equals(PromotionTypeEnum.GROUPBUY.name())
                        || promotionType.equals(PromotionTypeEnum.SECKILL.name())
                        || promotionType.equals(PromotionTypeEnum.SHETUAN.name())
                        || promotionType.equals(PromotionTypeEnum.HALF_PRICE.name())) {

                    //原价小计
                    Double originalSubTotal = CurrencyUtil.mul(skuVO.getOriginalPrice(), skuVO.getNum());

                    //总优惠 - 商品立减的优惠
                    discountTotalPrice = CurrencyUtil.sub(discountTotalPrice, CurrencyUtil.sub(originalSubTotal, skuVO.getSubtotal()));
                }
            }

            //将DTO转换DO
            OrderDO orderDO = new OrderDO(orderDTO);
            orderDO.setTradeSn(tradeVO.getTradeSn());
            orderDO.setOrderStatus(OrderStatusEnum.NEW.value());

             //为order设置钱包付款金额，只有orderList长度为1时才会设置
            if (walletPayPrice > 0) {
                orderDO.setWalletPayPrice(walletPayPrice);
                //设置支付方式id，支付插件id，支付方式名称：payment_method_id/payment_plugin_id/payment_method_name 为全部钱包支付
            }

            orderStatusChangeMsg.setOldStatus(OrderStatusEnum.NEW);
            orderStatusChangeMsg.setNewStatus(OrderStatusEnum.NEW);

            // 为orderDTO 赋默认值，这些值会在orderLineVO中使用
            orderDTO.setOrderStatus(orderDO.getOrderStatus());
            orderDTO.setPayStatus(orderDO.getPayStatus());
            orderDTO.setShipStatus(orderDO.getShipStatus());
            orderDTO.setCommentStatus(orderDO.getCommentStatus());
            orderDTO.setServiceStatus(orderDO.getServiceStatus());
            orderDTO.setCreateTime(DateUtil.getDateline());

            if (orderDO.getExpiryDay() != null) {
                orderDO.setOrderType(OrderTypeEnum.fuwu.name());
            }

            this.daoSupport.insert(orderDO);

            int orderId = this.daoSupport.getLastId("es_order");
            orderDO.setOrderId(orderId);
            orderDTO.setOrderId(orderId);

            //订单项入库
            for (CartSkuVO skuVO : orderDTO.getSkuList()) {
                OrderItemsDO item = new OrderItemsDO(skuVO);
                item.setOrderSn(orderDO.getSn());
                item.setTradeSn(orderDO.getTradeSn());
                this.daoSupport.insert(item);
            }

            //订单出库状态表
            for (String type : OrderOutTypeEnum.getAll()) {
                OrderOutStatus orderOutStatus = new OrderOutStatus();
                orderOutStatus.setOrderSn(orderDO.getSn());
                orderOutStatus.setOutType(type);
                orderOutStatus.setOutStatus(OrderOutStatusEnum.WAIT.name());
                this.orderOutStatusManager.add(orderOutStatus);
            }


            //发送amqp状态消息
            orderStatusChangeMsg.setOrderDO(orderDO);

            //发送订单创建消息
            this.amqpTemplate.convertAndSend(AmqpExchange.ORDER_STATUS_CHANGE,
                    AmqpExchange.ORDER_STATUS_CHANGE + "_ROUTING",
                    orderStatusChangeMsg);


            //记录日志
            OrderLogDO logDO = new OrderLogDO();
            logDO.setOrderSn(orderDO.getSn());
            logDO.setMessage("创建订单");
            logDO.setOpName(buyer.getUsername());
            logDO.setOpTime(DateUtil.getDateline());
            this.orderLogManager.add(logDO);
        }

        //如果有钱包支付金额，生成预支付单---》发送订单创建消息时，消费者中冻结金额
        if (walletPayPrice > 0) {
            //1.确定支付方式为钱包支付
            String paymentPluginId = PaymentPluginEnum.walletPayPlugin.toString();
            PaymentMethodDO paymentMethod = this.paymentMethodManager.getByPluginId(paymentPluginId);
            if (paymentMethod == null) {
                throw new ServiceException(PaymentErrorCode.E501.code(), "未找到相应的支付方式[" + paymentPluginId + "]");
            }
            //2.使用系统时间加4位随机数生成流程号 并保存支付单
            String billSn = System.currentTimeMillis() + "" + StringUtil.getRandStr(4);
            //3.生成钱包支付流水单
            PaymentBillDO paymentBill = new PaymentBillDO(orderList.get(0).getSn(), billSn, null, 0, TradeType.order.toString(),
                    paymentMethod.getMethodName(), walletPayPrice, paymentMethod.getPluginId());
            //4.将支付单入库
            this.paymentBillManager.add(paymentBill);
            //tradeVO加入支付单，用于一并存入redis中，冻结账户金额时使用，解决冻结金额时查询不到支付单
            tradeVO.setPaymentBillDO(paymentBill);
        }
    }

    private void checkWalletPayPrice(List<OrderDTO> orderList, double walletPayPrice, Double totalPrice, String accountMemberId) {

        if(walletPayPrice < 0){
            throw new RuntimeException("订单单生成异常，钱包支付金额不得为负");
        }

        //判断orderList长度，只有长度1，即1条交易单对应1条订单时才能使用钱包支付，否则报错
        if (orderList.size() > 1 && walletPayPrice > 0) {
            throw new RuntimeException("订单生成异常，多个订单无法使用钱包支付");
        }

        if(BigDecimal.valueOf(walletPayPrice).compareTo(BigDecimal.valueOf(totalPrice)) > 0){
            throw new RuntimeException("订单生成异常，钱包支付金额不得超过总金额");
        }

        //校验账户钱包可用金额是否够支付本次钱包抵扣
        if (walletPayPrice > 0) {
            AccountDTO accountDTO = accountManager.queryByMemberIdAndAccountType(accountMemberId, AccountTypeEnum.MEMBER_MASTER.getIndex());
            BigDecimal balanceAmount = accountDTO.getBalanceAmount();
            if (balanceAmount.compareTo(BigDecimal.valueOf(walletPayPrice)) < 0) {
                throw new RuntimeException("订单生成异常，账户可用余额不足");
            }
        }
    }
}
